package com.lyf.scm.plan.domain.repository;

import com.lyf.scm.common.constants.BigDataEsConstants;
import com.lyf.scm.common.constants.EsAggrFunTypeConstants;
import com.lyf.scm.common.enums.*;
import com.lyf.scm.common.model.SalesPlanSelectDTO;
import com.lyf.scm.plan.api.dto.EsExportAggregationDTO;
import com.lyf.scm.plan.api.dto.EsQueryDTO;
import com.lyf.scm.plan.api.dto.EsAggregationDTO;
import com.lyf.scm.plan.insfrastructure.db.dataobject.es.SalesPlanEsEntity;
import com.rome.arch.core.exception.RomeException;
import org.apache.commons.collections4.CollectionUtils;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.script.Script;
import org.elasticsearch.search.aggregations.AggregationBuilder;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.bucket.composite.CompositeAggregationBuilder;
import org.elasticsearch.search.aggregations.bucket.composite.CompositeValuesSourceBuilder;
import org.elasticsearch.search.aggregations.bucket.composite.TermsValuesSourceBuilder;
import org.elasticsearch.search.aggregations.bucket.terms.TermsAggregationBuilder;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage;
import org.springframework.data.elasticsearch.core.query.IndexQuery;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.data.elasticsearch.core.query.SearchQuery;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import static org.elasticsearch.index.query.QueryBuilders.rangeQuery;
import static org.elasticsearch.index.query.QueryBuilders.termQuery;

/**
 * description:
 *
 * @author: haodong
 * @data: 2020/10/19 11:38
 */
@Service
public class SalesPlanEsRepository {

    @Resource
    private ElasticsearchTemplate elasticsearchTemplate;


    public AggregatedPage<SalesPlanEsEntity> queryByGroup(EsAggregationDTO esAggregation) {
        //获取QueryBuilder
        BoolQueryBuilder queryBuilder = getBoolQueryBuilder(esAggregation.getEsQuery());
        //获取AggregationBuilders
        TermsAggregationBuilder groupDate = AggregationBuilders.terms(BigDataEsConstants.GROUP_DATE)
                .field(EsConmulEnum.DATE_KEYWORD.getFName()).size(Integer.MAX_VALUE).shardSize(Integer.MAX_VALUE);
        //获取SumAggregationBuilders
        List<EsSumColumnEnum> sumColumnList = EsSumColumnEnum.getEsSumColumnList(esAggregation.getSumColumns());
        getSumConditions(sumColumnList).forEach(groupDate::subAggregation);
        AggregatedPage<SalesPlanEsEntity> entities;
        if (CollectionUtils.isNotEmpty(esAggregation.getGroupColumns())) {
            TermsAggregationBuilder groupData = getGroupConditions(esAggregation.getGroupColumns());
            groupData.subAggregation(groupDate);
            entities = aggregationQuery(queryBuilder, groupData);
        } else {
            entities = aggregationQuery(queryBuilder, groupDate);
        }
        if (entities.getTotalElements() == 0) {
            return null;
        }
        return entities;
    }

    public AggregatedPage<SalesPlanEsEntity> exportByGroup(EsExportAggregationDTO esExportAggregationDTO) {
        //判断是否终止操作
        String afterKey = esExportAggregationDTO.getAfterKey();
        if (afterKey == null) {
            return null;
        }
        //获取QueryBuilder
        BoolQueryBuilder queryBuilder = getBoolQueryBuilder(esExportAggregationDTO.getEsQuery());
        //获取AggregationBuilders
        CompositeAggregationBuilder comAgg;
        if (CollectionUtils.isNotEmpty(esExportAggregationDTO.getGroupColumns())) {
            comAgg = getExportGroupConditions(esExportAggregationDTO.getGroupColumns());
            Map<String, Object> afterKeyMap = new HashMap<>();
            afterKeyMap.put(BigDataEsConstants.GROUP_MULTI, afterKey);
            comAgg.aggregateAfter(afterKeyMap);
        } else {
            throw new RomeException("999", "分组列至少存在一组");
        }
        TermsAggregationBuilder groupDate = AggregationBuilders.terms(BigDataEsConstants.GROUP_DATE)
                .field(EsConmulEnum.DATE_KEYWORD.getFName()).size(Integer.MAX_VALUE).shardSize(Integer.MAX_VALUE);
        //获取SumAggregationBuilders
        List<EsSumColumnEnum> sumColumnList = EsSumColumnEnum.getEsSumColumnList(esExportAggregationDTO.getSumColumns());
        getSumConditions(sumColumnList).forEach(groupDate::subAggregation);
        comAgg.subAggregation(groupDate);
        AggregatedPage<SalesPlanEsEntity> entities = exportAggregationQuery(queryBuilder, comAgg);
        if (entities.getTotalElements() == 0) {
            return null;
        }
        return entities;
    }

    public List<SalesPlanEsEntity> listPageQuery(EsQueryDTO esQuery, Integer pageNum, Integer pageSize) {
        BoolQueryBuilder queryBuilder = getBoolQueryBuilder(esQuery);
        SearchQuery query = new NativeSearchQueryBuilder()
                .withQuery(queryBuilder)
                .withIndices(BigDataEsConstants.INDEX_NAME_PLAN_SALES_PLAN)
                .withPageable(PageRequest.of(pageNum - 1, pageSize))
                .build();
        return elasticsearchTemplate.queryForList(query, SalesPlanEsEntity.class);
    }

    public AggregatedPage<SalesPlanEsEntity> aggregationQuery(BoolQueryBuilder queryBuilder, TermsAggregationBuilder aggrBuilder) {
        SearchQuery query = new NativeSearchQueryBuilder()
                .withQuery(queryBuilder)
                .withIndices(BigDataEsConstants.INDEX_NAME_PLAN_SALES_PLAN)
                .addAggregation(aggrBuilder)
                .withPageable(PageRequest.of(0,1))
                .build();
        return elasticsearchTemplate.queryForPage(query, SalesPlanEsEntity.class);
    }

    public AggregatedPage<SalesPlanEsEntity> exportAggregationQuery(BoolQueryBuilder queryBuilder, CompositeAggregationBuilder comAgg) {
        SearchQuery query = new NativeSearchQueryBuilder().withPageable(PageRequest.of(0, 1))
                .withQuery(queryBuilder)
                .withIndices(BigDataEsConstants.INDEX_NAME_PLAN_SALES_PLAN)
                .addAggregation(comAgg)
                .build();
        return elasticsearchTemplate.queryForPage(query, SalesPlanEsEntity.class);
    }

    public Boolean update(List<IndexQuery> indexQueryList) {
        elasticsearchTemplate.bulkIndex(indexQueryList);
        elasticsearchTemplate.refresh(BigDataEsConstants.INDEX_NAME_PLAN_SALES_PLAN);
        return Boolean.TRUE;
    }


    /**
     * 解析聚合类型
     *
     * @param sumColumns
     * @return
     */
    private List<AggregationBuilder> getSumConditions(List<EsSumColumnEnum> sumColumns) {
        // 指标sum聚合
        List<AggregationBuilder> sumList = new ArrayList<>();
        for (EsSumColumnEnum sumColumn : sumColumns) {
            switch (sumColumn.getType()) {
                case EsAggrFunTypeConstants.SUM:
                    sumList.add(AggregationBuilders.sum(sumColumn.getColumn()).script(new Script(sumColumn.getFormula())));
                    break;
                case EsAggrFunTypeConstants.MAX:
                    sumList.add(AggregationBuilders.max(sumColumn.getColumn()).script(new Script(sumColumn.getFormula())));
                    break;
                case EsAggrFunTypeConstants.MIN:
                    sumList.add(AggregationBuilders.min(sumColumn.getColumn()).script(new Script(sumColumn.getFormula())));
                    break;
                case EsAggrFunTypeConstants.AVG:
                    sumList.add(AggregationBuilders.avg(sumColumn.getColumn()).script(new Script(sumColumn.getFormula())));
                    break;
                case EsAggrFunTypeConstants.COUNT:
                    sumList.add(AggregationBuilders.count(sumColumn.getColumn()).script(new Script(sumColumn.getFormula())));
                    break;
                case EsAggrFunTypeConstants.CARDINALITY:
                    sumList.add(AggregationBuilders.cardinality(sumColumn.getColumn()).script(new Script(sumColumn.getFormula())));
                    break;
                default:
                    break;
            }
        }
        return sumList;
    }


    /**
     * 获取分组参数
     *
     * @param groupColumns
     * @return
     */
    private TermsAggregationBuilder getGroupConditions(List<String> groupColumns) {
        String grammar = "doc['&'].values";
        String spiltStr = "";
        StringBuilder scriptStr = new StringBuilder();
        for (String groupColumn : groupColumns) {
            String newGrammar = grammar.replace("&", groupColumn);
            scriptStr.append(spiltStr).append(newGrammar);
            spiltStr = "+'&&'+";
        }
        Script script = new Script(scriptStr.toString());
        return AggregationBuilders.terms(BigDataEsConstants.GROUP_DATA).script(script).size(700);
    }

    /**
     * 获取分组参数
     *
     * @param groupColumns
     * @return
     */
    private CompositeAggregationBuilder getExportGroupConditions(List<String> groupColumns) {
        String grammar = "doc['&'].values";
        String spiltStr = "";
        StringBuilder scriptStr = new StringBuilder();
        for (String groupColumn : groupColumns) {
            String newGrammar = grammar.replace("&", groupColumn);
            scriptStr.append(spiltStr).append(newGrammar);
            spiltStr = "+'&&'+";
        }
        Script script = new Script(scriptStr.toString());
        TermsValuesSourceBuilder dataBuilder = new TermsValuesSourceBuilder(BigDataEsConstants.GROUP_MULTI).script(script);
        List<CompositeValuesSourceBuilder<?>> sources = new ArrayList<>();
        sources.add(dataBuilder);
        //TODO 这里的返回组数用来测试
        return new CompositeAggregationBuilder(BigDataEsConstants.GROUP_PRI, sources).size(BigDataEsConstants.EXPORT_EACH_LENGTH);
    }

    /**
     * 获取查询条件
     *
     * @param baseSearch
     * @return
     */
    private BoolQueryBuilder getBoolQueryBuilder(EsQueryDTO baseSearch) {
        BoolQueryBuilder queryBuilder = QueryBuilders.boolQuery();
        queryBuilder.filter(rangeQuery(EsConmulEnum.DATE_KEYWORD.getFName()).gte(baseSearch.getStartDate())
                .lte(baseSearch.getEndDate()));
        //已取消的活动计划不展示
        queryBuilder.mustNot(termQuery(EsConmulEnum.CANCEL_KEYWORD.getFName(), SalesCancelStatusEnum.ALREADYCANCEL.getType()));
        if (CollectionUtils.isNotEmpty(baseSearch.getTypeList())) {
            queryBuilder.filter(getShouldQuery(EsConmulEnum.PLAN_TYPE.getFName(), baseSearch.getTypeList()));
        }
        if (CollectionUtils.isNotEmpty(baseSearch.getWarehouseCodeList())) {
            queryBuilder.filter(getShouldQuery(EsConmulEnum.WAREHOUSE_KEYWORD.getFName(), baseSearch.getWarehouseCodeList()));
        }
        if (CollectionUtils.isNotEmpty(baseSearch.getLayeredCodeList())) {
            queryBuilder.filter(getShouldQuery(EsConmulEnum.LEVEL_KEYWORD.getFName(), baseSearch.getLayeredCodeList()));
        }
        if (CollectionUtils.isNotEmpty(baseSearch.getChannelCodeList())) {
            queryBuilder.filter(getShouldQuery(EsConmulEnum.CHANNEL_KEYWORD.getFName(), baseSearch.getChannelCodeList()));
        }
        if (CollectionUtils.isNotEmpty(baseSearch.getPermissionChannelList())) {
            queryBuilder.filter(getShouldQuery(EsConmulEnum.CHANNEL_KEYWORD.getFName(), baseSearch.getPermissionChannelList()));
        }
        if (CollectionUtils.isNotEmpty(baseSearch.getQueryAreaCodeList())) {
            queryBuilder.filter(getAreaQuery(baseSearch.getQueryAreaCodeList()));
        }
        if (CollectionUtils.isNotEmpty(baseSearch.getPermissionAreaList())) {
            queryBuilder.filter(getAreaQuery(baseSearch.getPermissionAreaList()));
        }
        if (CollectionUtils.isNotEmpty(baseSearch.getCategoryCodeList())) {
            queryBuilder.filter(getCateQuery(baseSearch.getCategoryCodeList()));
        }
        return queryBuilder;
    }

    private QueryBuilder getCateQuery(List<SalesPlanSelectDTO> categoryCodeList) {
        BoolQueryBuilder queryBuilder = QueryBuilders.boolQuery();
        categoryCodeList.forEach(val -> {
            switch (val.getLevel()) {
                case 2:
                    queryBuilder.should(termQuery(EsConmulEnum.PRI_KEYWORD.getFName(), val.getCode()));
                    break;
                case 3:
                    queryBuilder.should(termQuery(EsConmulEnum.SEC_KEYWORD.getFName(), val.getCode()));
                    break;
                case 4:
                    queryBuilder.should(termQuery(EsConmulEnum.TER_KEYWORD.getFName(), val.getCode()));
                    break;
                case 5:
                    queryBuilder.should(termQuery(EsConmulEnum.SKU_KEYWORD.getFName(), val.getCode()));
                    break;
                default:
                    throw new RomeException(ResponseMsg.PARAM_ERROR.getCode(), ResponseMsg.PARAM_ERROR.getMsg());
            }
        });
        return queryBuilder;
    }

    private QueryBuilder getAreaQuery(List<SalesPlanSelectDTO> areaCodeList) {
        BoolQueryBuilder queryBuilder = QueryBuilders.boolQuery();
        areaCodeList.forEach(val -> {
            switch (val.getLevel()) {
                case 1:
                    queryBuilder.should(termQuery(EsConmulEnum.PROVINCE_CODE_KEYWORD.getFName(), val.getCode()));
                    break;
                case 2:
                    queryBuilder.should(termQuery(EsConmulEnum.CITY_CODE_KEYWORD.getFName(), val.getCode()));
                    break;
                case 3:
                    queryBuilder.should(termQuery(EsConmulEnum.COUNTY_CODE_KEYWORD.getFName(), val.getCode()));
                    break;
                case 4:
                    queryBuilder.should(termQuery(EsConmulEnum.STORE_KEYWORD.getFName(), val.getCode()));
                    break;
                default:
                    throw new RomeException(ResponseMsg.PARAM_ERROR.getCode(), ResponseMsg.PARAM_ERROR.getMsg());
            }
        });
        return queryBuilder;
    }

    private QueryBuilder getShouldQuery(String key, List<String> codeList) {
        BoolQueryBuilder queryBuilder = QueryBuilders.boolQuery();
        codeList.forEach(code -> queryBuilder.should(termQuery(key, code)));
        return queryBuilder;
    }
}
